/*
 * Code for blog.techhead.biz
 * Distributed under BSD-style license
 */

package biz.techhead.xml;

import biz.techhead.funcy.*;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

/**
 *
 * @author Jonathan
 */
public class XPathContext<T> {

    public XPathContext(T context) {
        this(XPathFactory.newInstance().newXPath(), context);
    }

    /**
     * 
     * @param xPath
     * @param context a org.xml.sax.InputSource or org.w3c.dom.Node
     *                representing the root of the context
     */
    public XPathContext(XPath xPath, T context) {
        this.xPath = xPath;
        this.context = context;
    }
    
    public XPath getXPath() { return xPath; }
    
    public T getContext() { return context; }
    
    public XPathContext<Node> get(String path) {
        Node n = getNode(path);
        return (n == null) ? null : new XPathContext<Node>(xPath, n);
    }
    
    private Node getNode(String path) throws XPathExpressionException {
        try {
            return (context instanceof InputSource) ?
                (Node) xPath.evaluate(path,
                    (InputSource)context, XPathConstants.NODE) :
                (Node) xPath.evaluate(path, context, XPathConstants.NODE);                
        } catch (javax.xml.xpath.XPathExpressionException ex) {
            throw new XPathExpressionException(ex);
        }
    }

    public String getString(String path) throws XPathExpressionException {
        try {
            return xPath.evaluate(path, context);
        } catch (javax.xml.xpath.XPathExpressionException ex) {
            throw new XPathExpressionException(ex);
        }
    }

    public Double getNumber(String path) throws XPathExpressionException {
        try {
            return (Double)
                    xPath.evaluate(path, context, XPathConstants.NUMBER);
        } catch (javax.xml.xpath.XPathExpressionException ex) {
            throw new XPathExpressionException(ex);
        }
    }

    public boolean is(String path) throws XPathExpressionException {
        try {
            Boolean b = (Boolean)
                    xPath.evaluate(path, context, XPathConstants.BOOLEAN);
            return (b == null) ? false : b.booleanValue();
        } catch (javax.xml.xpath.XPathExpressionException ex) {
            throw new XPathExpressionException(ex);
        }
    }

    public <E extends Throwable>
            void foreach(String path, FuncE<?,XPathContext<Node>,E> f)
            throws XPathExpressionException, E {
        NodeList nodes = null;        
        try {
            nodes = (NodeList)
                    xPath.evaluate(path, context, XPathConstants.NODESET);
        } catch (javax.xml.xpath.XPathExpressionException ex) {
            throw new XPathExpressionException(ex);
        }
        for (int i=0, len=nodes.getLength(); i < len; i++) {
            f.call( new XPathContext<Node>(xPath, nodes.item(i)) );
        }
    }

    private XPath xPath;
    private T context;
}
